package net.chrisrichardson.ec2deployer.cluster

import org.apache.commons.logging.*
import net.chrisrichardson.ec2deployer.core.CloudManager
import net.chrisrichardson.ec2deployer.core.EC2Server


class ClusterInstance {

    def Log logger = LogFactory.getLog(getClass())

    DatabaseTier databaseTier
    AppServerTier appServerTier
    WebServerTier webServerTier
    JmeterTier jmeterTier
    TerracottaTier terracottaTier

    CloudManager ec2
    ClusterSpec spec

    protected ClusterInstance() {}
    
    def ClusterInstance(CloudManager ec2, ClusterSpec spec, Map serverState = [:]) {
        this.ec2 = ec2
        this.spec = spec
        logger.debug("server state= " + serverState)
        
        databaseTier = new DatabaseTier(ec2, spec)
        terracottaTier = new TerracottaTier(ec2, spec)
        appServerTier = new AppServerTier(ec2, spec, databaseTier, terracottaTier)
        webServerTier = new WebServerTier(ec2, spec, appServerTier)
        appServerTier.webServerTier = webServerTier
        jmeterTier = new JmeterTier(ec2, spec, appServerTier, webServerTier, databaseTier, terracottaTier)

        if (serverState)
            restoreServerState(serverState)
    }

    List<Tier> getTiers() {
        List result = [databaseTier, terracottaTier, appServerTier, webServerTier, jmeterTier]
        result.size().times { result.remove null }
        result
    }

    def getServerState() {
        def result = new HashMap()
        tiers*.saveState(result)
        logger.debug("result=" + result)
        return result
    }

    def restoreServerState(Map serverState) {
        ec2.pollStatus()
        tiers*.restoreState(serverState)
    }

    Set<EC2Server> getServers() {
        tiers.inject(new HashSet()){ servers, tier -> servers + tier.servers}
    }

    def start(boolean reuseRunningInstances) {
        ec2.startPolling()
        allocateAndAssignHosts()
        appServerTier.uploadWebAppsToS3()
        startTiers()
        webServerTier.assignIpAddress()
        logger.info "Cluster started"
    }
    
    ClusterHosts allocateAndAssignHosts() {
      configureSecurityGroups()
      ClusterHosts hosts = spec.makeTopology().allocateServers(ec2, spec, false /* reuseRunningInstances */ )
      tiers*.assignHosts(hosts)
      hosts
    }

    void configureSecurityGroups() {
        if (!spec.securityGroupPrefix) return;

        String jmeterSecurityGroup = spec.securityGroupPrefix + "-" + "jmeter"
        String databaseSecurityGroup = spec.securityGroupPrefix + "-" + "database"
        String slaveSecurityGroup = spec.securityGroupPrefix + "-" + "dbslaves"
        String appServersSecurityGroup = spec.securityGroupPrefix + "-" + "appServers"
        String webServersSecurityGroup = spec.securityGroupPrefix + "-" + "webServers"
        String terracottaSecurityGroup = spec.securityGroupPrefix + "-" + "terracottaServers"

        ec2.configureSecurityGroup(jmeterSecurityGroup, [:])
        ec2.configureSecurityGroup(webServersSecurityGroup, ["$jmeterSecurityGroup": [80]])
        ec2.configureSecurityGroup(slaveSecurityGroup, [:])
        ec2.configureSecurityGroup(appServersSecurityGroup,  [ "$webServersSecurityGroup" : [8009, 8010] ])
        ec2.configureSecurityGroup(databaseSecurityGroup, ["$slaveSecurityGroup" : [3306], "$appServersSecurityGroup" : [3306]])

    }
    
    def startTiers() {
      waitUntilRunning()
      tiers*.start()
    }

    private void waitUntilRunning() {
        logger.info "waiting for servers to launch"
        def waitedFor = new HashSet()
        tiers*.waitUntilRunning(waitedFor)
    }


    def stop() {
        // TODO - stop has two meanings: stop the services, stop the instances
        tiers*.stop()
        servers*.stop()
    }

    def loadTest(jmxFileName, threadCounts, List<String> dataFiles) {
        jmeterTier.loadTest(jmxFileName, threadCounts, dataFiles)
    }

    void dumpInfo() {
        dumpServerInfo("MySql master", databaseTier.databaseServer);
        if (spec.ebsVolume) {
            println "EBS Volume: ${spec.ebsVolume.volumeId}"
        }
        dumpServerInfo("MySql slaves", databaseTier.mySqlSlaves)
        dumpServerInfo("Terracotta", terracottaTier.terracottaServers)
        dumpServerInfo("Tomcat", appServerTier.tomcats)
        dumpServerInfo("Apache", webServerTier.apacheServer)
        dumpServerInfo("Jmeter", jmeterTier.jmeter)
    }

    def dumpServerInfo(label, applicationOrApplications) {
        if (!applicationOrApplications)
            return
        println "$label:"
        applicationOrApplications.each {application -> println "   ${application.host.instanceId} ${application.host.publicDnsName} ${application.host.privateDnsName} ${application.host.availabilityZone}"}
    }

    def redeploy() {
        appServerTier.redeploy()
    }

    def dbSave(String s3Path) {
      databaseTier.dbSave(s3Path)
    }

    def dbRestore(String s3Path) {
      databaseTier.dbRestore(s3Path)
    }

    def dbRecreate(File sqlScript = null) {
         databaseTier.dbRecreate(sqlScript)
     }

    def executeSqlScripts(scripts) {
        databaseTier.executeSqlScripts scripts
    }

    boolean isAllInstancesRunning() {
        servers.inject(true) {boolean result, EC2Server server -> result && server.isRunning() } 
    }

    boolean isAllInstancesTerminated() {
        servers.inject(true) {boolean result, EC2Server server -> result && server.isTerminated() } 
    }

    void addAppServers(int n) {
        ec2.startPolling()
        appServerTier.addAppServers n
        webServerTier.start()
    }

    String snapshotDatabase() {
        databaseTier.snapshotDatabase()
    }

    def cloneCluster() {
        Map<String, Object> cloneState = [:]
        ClusterSpec clonedClusterSpec = spec.makeCopyForClone()

        tiers*.prepareToClone(cloneState, clonedClusterSpec)

        ClusterInstance clonedCluster = new ClusterInstance(ec2, clonedClusterSpec)

        clonedCluster.startAfterCloning(cloneState)

        clonedCluster
    }

  ClusterCheckpoint createCheckpoint() {
    Map<String, Object> cloneState = [:]
    ClusterSpec clonedClusterSpec = spec.makeCopyForClone()
    tiers*.prepareToClone(cloneState, clonedClusterSpec)
    new ClusterCheckpoint(cloneState, clonedClusterSpec)
  }

    void startAfterCloning(Map<String, Object> cloneState) {
        allocateAndAssignHosts()
        waitUntilRunning()
        tiers*.startAfterCloning(cloneState)
    }
}